/**
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

package pittypat.configuration;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import pittypat.IdentifierHelper;

/**
 * Pittypat 配置文件的根。
 */
public final class PittypatConfig {
	private static final Logger LOGGER = LogManager.getLogger(PittypatConfig.class);

	private static PittypatConfig current = null;

	/**
	 * 获取当前配置文件的根。
	 */
	public static PittypatConfig getCurrent() {
		// 在 PittypatConfig.init 中初始化
		return current;
	}

	// 一个空的 AppConfig 数组。
	private static final AppConfig[] EMPTY_APPS = new AppConfig[0];
	// 一个空的 ModuleConfig 数组。
	private static final ModuleConfig[] EMPTY_MODULES = new ModuleConfig[0];
	// 一个空的 FunctionConfig 数组。
	private static final FunctionConfig[] EMPTY_FUNCTIONS = new FunctionConfig[0];

	// 解析单个功能的配置信息。
	private static FunctionConfig parseFunction(Element functionElement) {
		// 功能的标识符
		String identifier = functionElement.getAttribute("identifier");

		// 忽略无效的功能标识符
		if (identifier == null || identifier.length() == 0) {
			LOGGER.info("配置文件中发现没有提供标识符的功能配置项目，该配置项目已忽略。");
			return null;
		}

		// 忽略无效的功能标识符
		if (!IdentifierHelper.isIdentifier(identifier)) {
			LOGGER.info(identifier + " 不是有效的功能标识符，该配置项目已忽略。");
			return null;
		}

		// 上传文件的尺寸，单位：KB，最小 1KB，最大 2GB，默认为 100MB
		int maxUploadSize = 102400;
		String s = functionElement.getAttribute("maxUploadSize");

		if (s != null && s.length() > 0) {
			try {
				maxUploadSize = Integer.parseInt(s);
			} catch (NumberFormatException exc) {
				LOGGER.error(exc);
			}

			// 确保文件尺寸介于 1KB 到 2GB 之间
			maxUploadSize = maxUploadSize < 1 ? 1 : (maxUploadSize > 2097152 ? 2097152 : maxUploadSize);
		}

		// 创建一个新的 FunctionConfig 对象
		return new FunctionConfig(identifier, functionElement.getAttribute("uploadPath"), maxUploadSize,
				functionElement.getAttribute("uploadExtensions"));
	}

	// 从指定的模块配置节点中解析功能配置列表。
	private static FunctionConfig[] parseFunctions(Element moduleElement) {
		// 模块中包含的功能配置信息
		ArrayList<FunctionConfig> list = new ArrayList<FunctionConfig>();

		// 功能配置节点列表
		NodeList nodes = moduleElement.getElementsByTagName("function");
		FunctionConfig functionConfig = null;

		for (int i = 0; i < nodes.getLength(); ++i) {
			// 对每一个功能节点解析一个功能配置信息，返回 null 表示配置信息无效而忽略配置节点
			functionConfig = parseFunction((Element) nodes.item(i));
			if (functionConfig != null) {
				list.add(functionConfig);
			}
		}

		if (list.size() == 0) {
			return EMPTY_FUNCTIONS;
		}

		// 转换为功能配置信息的数组
		FunctionConfig[] functions = new FunctionConfig[list.size()];
		list.toArray(functions);
		return functions;
	}

	// 解析单个模块的配置信息。
	private static ModuleConfig parseModule(Element moduleElement) {
		String identifier = moduleElement.getAttribute("identifier");

		// 忽略无效的模块标识符
		if (identifier == null || identifier.length() == 0) {
			LOGGER.info("配置文件中发现没有提供标识符的模块配置项目，该配置项目已忽略。");
			return null;
		}

		// 忽略无效的模块标识符
		if (!IdentifierHelper.isIdentifier(identifier)) {
			LOGGER.info(identifier + " 不是有效的模块标识符，该配置项目已忽略。");
			return null;
		}

		// 创建一个新的 FunctionConfig 对象
		return new ModuleConfig(identifier, moduleElement.getAttribute("dataServiceType"),
				moduleElement.getAttribute("dataServiceUrl"), moduleElement.getAttribute("dataSchema"),
				moduleElement.getAttribute("status"), parseFunctions(moduleElement));
	}

	// 从指定的应用配置节点中解析模块配置列表。
	private static ModuleConfig[] parseModules(Element appElement) {
		// 应用中包含的模块配置信息
		ArrayList<ModuleConfig> list = new ArrayList<ModuleConfig>();

		// 模块配置节点列表
		NodeList nodes = appElement.getElementsByTagName("module");
		ModuleConfig moduleConfig = null;

		for (int i = 0; i < nodes.getLength(); ++i) {
			// 对每一个模块节点解析一个模块配置信息，返回 null 表示配置信息无效而忽略配置节点
			moduleConfig = parseModule((Element) nodes.item(i));
			if (moduleConfig != null) {
				list.add(moduleConfig);
			}
		}

		if (list.size() == 0) {
			return EMPTY_MODULES;
		}

		// 转换为模块配置信息的数组
		ModuleConfig[] modules = new ModuleConfig[list.size()];
		list.toArray(modules);
		return modules;
	}

	// 解析单个应用的配置信息。
	private static AppConfig parseApp(Element appElement) {
		String developer = appElement.getAttribute("developer");

		// 忽略无效的开发者代码
		if (developer == null || developer.length() == 0) {
			LOGGER.info("配置文件中发现没有提供开发者代码的应用配置项目，该配置项目已忽略。");
			return null;
		}

		// 忽略无效的开发者代码
		if (!IdentifierHelper.isIdentifier(developer)) {
			LOGGER.info(developer + " 不是有效的开发者代码，该配置项目已忽略。");
			return null;
		}

		String identifier = appElement.getAttribute("identifier");

		// 忽略无效的应用标识符
		if (identifier == null || identifier.length() == 0) {
			LOGGER.info("配置文件中发现没有提供标识符的应用配置项目，该配置项目已忽略。");
			return null;
		}

		// 忽略无效的应用标识符
		if (!IdentifierHelper.isIdentifier(identifier)) {
			LOGGER.info(identifier + " 不是有效的应用标识符，该配置项目已忽略。");
			return null;
		}

		return new AppConfig(developer, identifier, appElement.getAttribute("status"), parseModules(appElement));
	}

	// 从指定的 apps 配置节点中解析应用配置列表。
	private static AppConfig[] parseApps(Element appsElement) {
		// 所有应用配置信息
		ArrayList<AppConfig> list = new ArrayList<AppConfig>();

		// 应用配置节点列表
		NodeList nodes = appsElement.getElementsByTagName("app");
		AppConfig appConfig = null;

		for (int i = 0; i < nodes.getLength(); ++i) {
			// 对每一个应用节点解析一个应用配置信息，返回 null 表示配置信息无效而忽略配置节点
			appConfig = parseApp((Element) nodes.item(i));
			if (appConfig != null) {
				list.add(appConfig);
			}
		}

		if (list.size() == 0) {
			return EMPTY_APPS;
		}

		// 转换为应用配置信息的数组
		AppConfig[] apps = new AppConfig[list.size()];
		list.toArray(apps);
		return apps;
	}

	/**
	 * 解析指定路径的配置文件，并初始化
	 * PittypatConfig.Current。如果此前已经解析并初始化过当前配置信息，则直接返回已经解析的配置信息。
	 * 
	 * @param path
	 *            配置文件的物理路径。
	 * 
	 * @throws ParserConfigurationException
	 * @throws URISyntaxException
	 * @throws IOException
	 * @throws SAXException
	 * @return 已经解析成功的配置信息，如果解析失败，则返回 null。
	 */
	public static PittypatConfig init(String path) throws ParserConfigurationException, SAXException, IOException,
			URISyntaxException, IllegalAccessException, InstantiationException, ClassNotFoundException {
		if (current == null) {
			// 为读取 xml 文件做准备
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

			factory.setIgnoringComments(true);
			factory.setIgnoringElementContentWhitespace(true);
			factory.setValidating(false);

			DocumentBuilder builder = factory.newDocumentBuilder();

			// 读取并解析 xml 文件
			Document doc = builder.parse(new File(path));
			Element root = doc.getDocumentElement();
			root.normalize();

			// socketDataSize，默认为 8KB
			int socketDataSize = 8;
			String s = root.getAttribute("socketDataSize");
			if (s != null && s.length() > 0) {
				socketDataSize = Integer.parseInt(s, 10);
				// 确保 socketDataSize 介于 1KB 和 1MB 之间
				socketDataSize = socketDataSize < 1 ? 1 : (socketDataSize > 1024 ? 1024 : socketDataSize);
			}

			// 应用配置项目列表
			NodeList nodes = root.getElementsByTagName("apps");

			current = new PittypatConfig(socketDataSize, root.getAttribute("authorityService"),
					root.getAttribute("systemTokenProvider"), root.getAttribute("notificationService"),
					root.getAttribute("dataServiceUrlDecryptor"),
					nodes.getLength() == 0 ? EMPTY_APPS : parseApps((Element) nodes.item(0)));
		}

		return current;
	}

	// WebSocket 缓冲区的尺寸，单位：KB
	private int socketDataSize;
	// 对用户身份进行验证的服务的类型名称
	private String authorityService;
	// 系统令牌提供程序的类型名称
	private String systemTokenProvider;
	// 通知中心服务的类型名称
	private String notificationService;
	// 解密数据服务路径（连接字符串）的解密程序的类型名称
	private String dataServiceUrlDecryptor;
	// 所有应用的配置列表
	private AppConfig[] apps;

	/**
	 * 初始化 PittypatConfig 类的新实例。
	 * 
	 * @param socketDataSize
	 *            WebSocket 缓冲区的尺寸，单位：KB。
	 * @param authorityService
	 *            对用户身份进行验证的服务的类型名称。
	 * @param systemTokenProvider
	 *            系统令牌提供程序的类型名称。
	 * @param notificationService
	 *            通知中心服务的类型名称。
	 * @param dataServiceUrlDecryptor
	 *            解密数据服务路径（连接字符串）的解密程序的类型名称。
	 * @param apps
	 *            所有应用的配置列表。
	 */
	private PittypatConfig(int socketDataSize, String authorityService, String systemTokenProvider,
			String notificationService, String dataServiceUrlDecryptor, AppConfig[] apps) {
		this.socketDataSize = socketDataSize < 1 ? 1 : socketDataSize;
		this.authorityService = authorityService == null ? "" : authorityService;
		this.systemTokenProvider = systemTokenProvider == null ? "" : systemTokenProvider;
		this.notificationService = notificationService == null ? "" : notificationService;
		this.dataServiceUrlDecryptor = dataServiceUrlDecryptor == null ? "" : dataServiceUrlDecryptor;
		this.apps = apps;
	}

	/**
	 * 获取当前系统使用的为 IAuthorityService 接口提供系统令牌的提供程序的类型名称。默认为空字符串。
	 * <p>
	 * 如果当前系统使用了来自其他系统的登录、注销、权限检查等安全验证服务，则当前系统可能访问其他系统时需要提供一个系统令牌来标识当前系统。也就是
	 * IAuthorityService 接口方法中使用的系统令牌。
	 * 
	 * @return当前系统的系统令牌。
	 */
	public String getSystemTokenProvider() {
		return this.systemTokenProvider;
	}

	/**
	 * 获取 WebSocket 缓冲区的尺寸，单位：KB。该配置值同时决定客户端在上传文件时对文件进行分片的尺寸。
	 * 
	 * @return WebSocket 缓冲区的尺寸。
	 */
	public int getSocketDataSize() {
		return this.socketDataSize;
	}

	/**
	 * 获取用户验证服务的实现类型，这是一个程序集限定的名称，如果没有提供，则认为所有用户都是未授权的匿名用户。
	 * 
	 * @return 用于提供用户验证服务的类型的名称。
	 */
	public String getAuthorityService() {
		return this.authorityService;
	}

	/**
	 * 获取通知中心服务的实现类型。
	 * 
	 * @return 通知中心服务的实现类型的名称。
	 */
	public String getNotificationService() {
		return this.notificationService;
	}

	/**
	 * 获取用于解密数据服务路径的解密程序的类型，这是一个程序集限定的名称，如果没有提供，则认为路径没有被加密，不对路径进行解密。
	 * 
	 * @return 用于解密数据服务路径的解密程序的类型的名称。
	 */
	public String getDataServiceUrlDecryptor() {
		return this.dataServiceUrlDecryptor;
	}

	/**
	 * 获取所有应用的配置列表。
	 * 
	 * @return 所有应用的配置列表。
	 */
	public AppConfig[] getApps() {
		return this.apps;
	}

	/**
	 * 获取指定开发者代码和标识符的应用配置信息。
	 * 
	 * @param developerCode
	 *            开发者代码，不区分大小写。
	 * @param identifier
	 *            应用标识符，不区分大小写。
	 * @return 具有指定开发者代码和标识符的应用配置信息，如果没有找到，则返回 null。
	 */
	public AppConfig getAppConfig(String developerCode, String identifier) {
		for (AppConfig c : this.apps) {
			if (c.getDeveloper().equalsIgnoreCase(developerCode) && c.getIdentifier().equalsIgnoreCase(identifier)) {
				return c;
			}
		}

		return null;
	}
}
